{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "b7a90935",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<bound method MNIST.__len__ of Dataset FashionMNIST\n",
      "    Number of datapoints: 60000\n",
      "    Root location: data\n",
      "    Split: Train\n",
      "    StandardTransform\n",
      "Transform: ToTensor()> \n",
      " <bound method MNIST.__getitem__ of Dataset FashionMNIST\n",
      "    Number of datapoints: 10000\n",
      "    Root location: data\n",
      "    Split: Test\n",
      "    StandardTransform\n",
      "Transform: ToTensor()>\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor\n",
    "from torch import nn\n",
    "\n",
    "\n",
    "device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else \"cpu\"\n",
    "\n",
    "# 自动下载老出错，我直接到 github 下载解压了\n",
    "training_data = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=False,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "test_data = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=False,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "print(training_data.__len__, '\\n', test_data.__getitem__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b126fd73",
   "metadata": {},
   "source": [
    "虽然调用的方式有些错误……哈哈，torch 的文档里说 `All datasets are subclasses of torch.utils.data.Dataset i.e, they have __getitem__ and __len__ methods implemented.`，也就是我们不需要自己写 `Dataset` 类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c867cb07",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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yocffljm9+s0paWl0rlzZ3nyySeN25OTk497HybNmjWTtm3byrJly3zlA9VJWFiYdOvWTbp16yZt2rSRm266Sd5++2254YYbpH///tKuXTt58sknJTk5WcLCwmThwoXy1FNPlWvIqOharohDhw5J7969JTY2Vh566CFp1aqVREREyLfffiv33nuvsRkEOFXFxsZKkyZN5Mcff6xwTkhIiHU7a6jqOVH4zZw5UxITE+X5558vt23OnDny3nvvyZQpU9RmDJuQkBCZOXOmXHbZZXLVVVfJokWLjjvfrlWrVvL9999L//79j7tIglVcXFymmzglJUU2bNhQ7nZHT6enpKQE/rt27VopLS0tc9bv17er7OMFKqJr164iIrJ7926ZP3++FBYWyrx588qczavIr0hoUlJSZNOmTeXiv147//nPf2T//v0yZ84cueCCCwLxY3+SANQkQ4YMkalTp8oXX3wh55xzjnq7lJQUKS0tlU2bNgV+UiTyS4PhoUOHAt8hwawhvm9OjBr/O34FBQUyZ84cGTJkiAwfPrzcnzFjxkhOTk6Z3x8KVlhYmMyZM0e6desml156qaxatcp6+6uvvlp27twpL730kvF48/LyfB3Hxo0bZcOGDXLGGWcEYoMGDZJVq1bJF198EYjl5eXJ1KlTJTU1VTp06BC43Z49e+Stt94K3K64uFieffZZiYmJkd69e4uISFRUlIj88q82oLItXbrUeCbu6O8PtW3bNnAG79jbZWVlyfTp033vd9CgQfLll1+WWbv79u0rN/LJtO+ioiJ54YUXfO8bqM4mTJgg0dHRcsstt0hGRka57Vu2bJFnnnlGBg0aJCJS7kobR3+yNXjwYBEJbg1FR0fzo98ToMaf8Zs3b57k5OTI0KFDjdt79uwpCQkJMnPmTBkxYoTv/URGRsoHH3wg/fr1k0suuUQ+++wzdRzFjTfeKLNnz5bbb79dli5dKr169ZKSkhJZv369zJ49WxYvXhw4w6EpLi6W119/XUR++dFxenq6TJkyRUpLS8sMpb7vvvvkzTfflEsuuUTGjRsn9evXl1deeUXS0tLk3XffDZzdu+222+TFF1+UUaNGyTfffCOpqanyzjvvyIoVK+Tpp58O/HJvZGSkdOjQQd566y1p06aN1K9fXzp16mQdvQFU1NixYyU/P1+uuOIKadeunRQVFcnKlSvl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FStWWB/LyJEjPREp96dp06beN998U+a2R9fqsfvKycnxWrRo4aWmpnolJSWe53ne008/7YmI9/rrrwduV1RU5J1zzjleTEyMl52d7Xme5+3bt88TEW/ixInWY6xJ+FFvBV144YXyxRdfyNChQ+X777+Xxx57TAYOHChNmzaVefPmBW537L+IsrKyJDMzU3r37i1bt26VrKysMvfZoUMHOf/88wN/T0hIkLZt28rWrVsDsYULF0rPnj2le/fuZW53/fXXlzvGY/edk5MjmZmZcv7550t+fr6sX7/+tz0BwEnAOgMqZubMmdKoUSPp27eviIiEhITIiBEjZNasWcZfOxgxYoTEx8cH/n50TRy7Do5aunSpDBw4UPr37y9z5syR8PBw67G8/fbb0r59e2nXrp1kZmYG/vTr1y9wf8cTEREhS5YskSVLlsjixYvlxRdflJiYGBk0aJBs3LgxcLuFCxdK9+7d5bzzzgvEYmJi5LbbbpP09PRAF/DChQslKSlJrr322sDtQkNDZdy4cZKbmyufffbZcY+ppqLwC0K3bt1kzpw5cvDgQVm1apXcf//9kpOTI8OHDw+82VasWCEDBgyQ6OhoqVevniQkJMif//xnEZFyX0jNmzcvt4/4+Hg5ePBg4O/btm2T0047rdzt2rZtWy62bt06ueKKKyQuLk5iY2MlISEh8Muyv943UF2xzgC7kpISmTVrlvTt21fS0tJk8+bNsnnzZunRo4dkZGTIJ598Ui7n1+vgaBF47DoQETl8+LAMHjxYzjrrLJk9e7aEhYUd93g2bdok69atk4SEhDJ/2rRpIyIVa8yqXbu2DBgwQAYMGCAXXXSR3HbbbfLxxx9LVlaW3H///YHbbdu2zbgu27dvH9h+9L+nnXaa1KpVy3o7F/E7fj6EhYVJt27dpFu3btKmTRu56aab5O2335YbbrhB+vfvL+3atZMnn3xSkpOTJSwsTBYuXChPPfVUuV8Ur127tvH+Pc8L+pgOHTokvXv3ltjYWHnooYekVatWEhERId9++63ce++9xl9SB6oz1hlg9umnn8ru3btl1qxZMmvWrHLbZ86cKRdddFGZWEXXQXh4uAwaNEjmzp0rH374YZnfr9OUlpZK586d5cknnzRuT05OPu59mDRr1kzatm0ry5Yt85UPMwq/36hr164iIrJ7926ZP3++FBYWyrx588r866oip7k1KSkpsmnTpnLxDRs2lPn7f/7zH9m/f7/MmTNHLrjggkA8LS3N976B6oJ1Bvx/M2fOlMTERHn++efLbZszZ4689957MmXKFLUZwyYkJERmzpwpl112mVx11VWyaNGi4863a9WqlXz//ffSv39/CQkJCXqfNsXFxWW6iVNSUsqtSxEJ/JpFSkpK4L9r166V0tLSMmf9fn27yj7eUwE/6q2gpUuXGs8QLFy4UER++ZHQ0X9RHXu7rKwsmT59uu/9Dho0SL788ktZtWpVILZv375ybfumfRcVFckLL7zge9/AycY6A+wKCgpkzpw5MmTIEBk+fHi5P2PGjJGcnJwyvxMbrLCwMJkzZ45069ZNLr300jLrwuTqq6+WnTt3yksvvWQ83ry8PF/HsXHjRtmwYYOcccYZgdigQYNk1apV8sUXXwRieXl5MnXqVElNTZUOHToEbrdnzx556623ArcrLi6WZ599VmJiYqR3794iIhIVFSUiv5zNdwVn/Cpo7Nixkp+fL1dccYW0a9dOioqKZOXKlfLWW29Jamqq3HTTTZKRkSFhYWFy6aWXyujRoyU3N1deeuklSUxMlN27d/va74QJE+S1116Tiy++WMaPHx8YM3H0XzNHnXvuuRIfHy8jR46UcePGSUhIiLz22mu+fpwFVBXWGWA3b948ycnJkaFDhxq39+zZUxISEmTmzJkyYsQI3/uJjIyUDz74QPr16yeXXHKJfPbZZ+qIpRtvvFFmz54tt99+uyxdulR69eolJSUlsn79epk9e7YsXrw4cNZeU1xcLK+//rqI/PKj4/T0dJkyZYqUlpaWGUp93333yZtvvimXXHKJjBs3TurXry+vvPKKpKWlybvvvhs4u3fbbbfJiy++KKNGjZJvvvlGUlNT5Z133pEVK1bI008/LXXr1g08zg4dOshbb70lbdq0kfr160unTp2s46ROeVXUTXzKWbRokff73//ea9eunRcTE+OFhYV5rVu39saOHetlZGQEbjdv3jzv9NNP9yIiIrzU1FTv0Ucf9V5++eVyIyFSUlK8wYMHl9vPr9vkPc/z1q5d6/Xu3duLiIjwmjZt6j388MPetGnTyt3nihUrvJ49e3qRkZFekyZNAqMwRMRbunRp4HaMmUB1xToD7C699FIvIiLCy8vLU28zatQoLzQ01MvMzAyMc/nXv/5V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LyIie/fulTfeeEM2bdok9913n8TGxsrgwYPlySeflIsvvliuu+462bt3rzz//PPSunVrWbt2beB+unTpIldeeaU8/fTTsn//funZs6d89tlnsnHjRhEp+ztKgItYa4B/v//97+XJJ5+UgQMHys033yx79+6VKVOmSMeOHSU7Ozuo+0pISJD7779fJk2aJBdffLEMHTpUNmzYIC+88IJ069atzIglEZGzzz5bWrduLQ888IAUFhaW+TGvyC+/wzd58mS58cYb5eyzz5ZrrrlGEhIS5Oeff5YFCxZIr1695LnnnvvNz4EzqrqtuKYyjZiIiIjwzjzzTG/y5MleaWlp4LbTpk3zTjvtNC88PNxr166dN336dG/ixIner1+evLw876677vLq16/vxcTEeJdffrm3YcMGT0S8f/7znyf7IQLVAmsNsDu6RlavXm293euvv+61bNnSCwsL884880xv8eLFvsa5HPXcc8957dq180JDQ71GjRp5d9xxh3fw4EHjvh944AFPRLzWrVurx7d06VJv4MCBXlxcnBcREeG1atXKGzVqlPf1118HbjNy5EgvOjra+jhdF+J5ht/mxCljzZo1ctZZZ8nrr7/OdHLgBGKtAagJ+B2/U0hBQUG52NNPPy21atWSCy64oAqOCKiZWGsAaip+x+8U8thjj8k333wjffv2lTp16siiRYtk0aJFctttt0lycnJVHx5QY7DWANRU/Kj3FLJkyRKZNGmS/PTTT5KbmyvNmzeXG2+8UR544IFyc4wA+MdaA1BTUfgBAAA4gt/xAwAAcASFHwAAgCMo/AAAABxR4d9SZlq9zvbL3kevFfprZ5xxhpqjXYvw9ddfV3MyMjKMcduFqo9e+Npl1fFXXFlrUuZauscqLCys1P1oF6zfs2ePmrNjx46g9xMaGmqMH3v5uF+raeuTtXZqOfPMM9VtV155pTFu+75JSEgwxm1rICcnxxifOnWqmlOZ19S2vT+q4/v5qOMdG2f8AAAAHEHhBwAA4AgKPwAAAEdQ+AEAADiiwgOc+SVY/RdXbb+EHRMTY4z36tVLzVm8eLExftVVV6k5H3/8sTF+8OBBNcfP46lpquMv6Lqy1iq78ehPf/qTMX7aaaepOdovgsfFxak5jRo1MsZtv3C+atUqY7yyX+vq+H4+qjoemytrzebBBx80xidOnKjmHDlyxBi3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",
      "text/plain": [
       "<Figure size 800x800 with 9 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "labels_map = {\n",
    "    0: \"T-Shirt\",\n",
    "    1: \"Trouser\",\n",
    "    2: \"Pullover\",\n",
    "    3: \"Dress\",\n",
    "    4: \"Coat\",\n",
    "    5: \"Sandal\",\n",
    "    6: \"Shirt\",\n",
    "    7: \"Sneaker\",\n",
    "    8: \"Bag\",\n",
    "    9: \"Ankle Boot\",\n",
    "}\n",
    "figure = plt.figure(figsize=(8, 8))\n",
    "cols, rows = 3, 3\n",
    "for i in range(1, cols * rows + 1):\n",
    "    sample_idx = torch.randint(len(training_data), size=(1,)).item()\n",
    "    img, label = training_data[sample_idx]\n",
    "    figure.add_subplot(rows, cols, i)\n",
    "    plt.title(labels_map[label])\n",
    "    plt.axis(\"off\")\n",
    "    plt.imshow(img.squeeze(), cmap=\"gray\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "83861801",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([256, 1, 28, 28])\n"
     ]
    }
   ],
   "source": [
    "train_dataloader = torch.utils.data.DataLoader(dataset=training_data, batch_size=256, shuffle=True)\n",
    "test_dataloader = torch.utils.data.DataLoader(dataset=test_data, batch_size=256)\n",
    "\n",
    "print(next(iter(train_dataloader))[0].shape)\n",
    "# 可以看到是单通道的黑白图像\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "272905f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyLeNet(nn.Module):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.net = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2), nn.Sigmoid(),\n",
    "            nn.AvgPool2d(kernel_size=2, stride=2),\n",
    "            nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5), nn.Sigmoid(),\n",
    "            nn.AvgPool2d(kernel_size=2, stride=2),\n",
    "            nn.Flatten(),\n",
    "            nn.Linear(in_features=16*5*5, out_features=120, bias=True), nn.Sigmoid(),\n",
    "            nn.Linear(in_features=120, out_features=84, bias=True), nn.Sigmoid(),\n",
    "            nn.Linear(in_features=84, out_features=10, bias=True)\n",
    "        )\n",
    "    \n",
    "    def forward(self, x):\n",
    "        return self.net(x)\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "2b2a0d1a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Conv2d output shape: \t torch.Size([1, 6, 28, 28])\n",
      "Sigmoid output shape: \t torch.Size([1, 6, 28, 28])\n",
      "AvgPool2d output shape: \t torch.Size([1, 6, 14, 14])\n",
      "Conv2d output shape: \t torch.Size([1, 16, 10, 10])\n",
      "Sigmoid output shape: \t torch.Size([1, 16, 10, 10])\n",
      "AvgPool2d output shape: \t torch.Size([1, 16, 5, 5])\n",
      "Flatten output shape: \t torch.Size([1, 400])\n",
      "Linear output shape: \t torch.Size([1, 120])\n",
      "Sigmoid output shape: \t torch.Size([1, 120])\n",
      "Linear output shape: \t torch.Size([1, 84])\n",
      "Sigmoid output shape: \t torch.Size([1, 84])\n",
      "Linear output shape: \t torch.Size([1, 10])\n",
      "------------------------------------------------------------\n",
      "Sequential output shape: \t torch.Size([1, 10])\n"
     ]
    }
   ],
   "source": [
    "model = MyLeNet().to(device)\n",
    "X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32).to(device)\n",
    "for layer in model.net:\n",
    "    X = layer(X)\n",
    "    print(layer.__class__.__name__,'output shape: \\t',X.shape)\n",
    "\n",
    "print(\"------------------------------------------------------------\")\n",
    "\n",
    "X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32).to(device)\n",
    "X = model.net(X)\n",
    "print(model.net.__class__.__name__,'output shape: \\t',X.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "8f6745f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_loop(dataloader, model, loss_fn, optimizer):\n",
    "    size = len(dataloader.dataset)\n",
    "    # Set the model to training mode - important for batch normalization and dropout layers\n",
    "    # Unnecessary in this situation but added for best practices\n",
    "    model.train()\n",
    "    for batch, (X, y) in enumerate(dataloader):\n",
    "        # Compute prediction and loss\n",
    "        X, y = X.to(device), y.to(device)\n",
    "        pred = model(X)\n",
    "        loss = loss_fn(pred, y)\n",
    "\n",
    "        # Backpropagation\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        optimizer.zero_grad()\n",
    "\n",
    "        if batch % 100 == 0:\n",
    "            loss, current = loss.item(), batch * batch_size + len(X)\n",
    "            print(f\"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]\")\n",
    "\n",
    "\n",
    "def test_loop(dataloader, model, loss_fn):\n",
    "    # Set the model to evaluation mode - important for batch normalization and dropout layers\n",
    "    # Unnecessary in this situation but added for best practices\n",
    "    model.eval()\n",
    "    size = len(dataloader.dataset)\n",
    "    num_batches = len(dataloader)\n",
    "    test_loss, correct = 0, 0\n",
    "\n",
    "    # Evaluating the model with torch.no_grad() ensures that no gradients are computed during test mode\n",
    "    # also serves to reduce unnecessary gradient computations and memory usage for tensors with requires_grad=True\n",
    "    with torch.no_grad():\n",
    "        for X, y in dataloader:\n",
    "            X, y = X.to(device), y.to(device)\n",
    "            pred = model(X)\n",
    "            test_loss += loss_fn(pred, y).item()\n",
    "            correct += (pred.argmax(1) == y).type(torch.float).sum().item()\n",
    "\n",
    "    test_loss /= num_batches\n",
    "    correct /= size\n",
    "    print(f\"Test Error: \\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "5bf9911c",
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'model' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mNameError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[9]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m      1\u001b[39m learning_rate = \u001b[32m0.9\u001b[39m\n\u001b[32m      2\u001b[39m loss_fn = nn.CrossEntropyLoss()\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m optimizer = torch.optim.SGD(\u001b[43mmodel\u001b[49m.parameters(), lr=learning_rate)\n\u001b[32m      4\u001b[39m batch_size = \u001b[32m256\u001b[39m\n\u001b[32m      6\u001b[39m epochs = \u001b[32m10\u001b[39m\n",
      "\u001b[31mNameError\u001b[39m: name 'model' is not defined"
     ]
    }
   ],
   "source": [
    "learning_rate = 0.9\n",
    "loss_fn = nn.CrossEntropyLoss()\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)\n",
    "batch_size = 256\n",
    "\n",
    "epochs = 10\n",
    "for t in range(epochs):\n",
    "    print(f\"Epoch {t+1}\\n-------------------------------\")\n",
    "    train_loop(train_dataloader, model, loss_fn, optimizer)\n",
    "    test_loop(test_dataloader, model, loss_fn)\n",
    "print(\"Done!\")\n",
    "\n",
    "torch.save(model.state_dict(), 'model_weights.pth')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "2d636d70",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1\n",
      "-------------------------------\n",
      "loss: 0.515316  [  256/60000]\n",
      "loss: 0.469200  [25856/60000]\n",
      "loss: 0.417957  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 82.0%, Avg loss: 0.475867 \n",
      "\n",
      "Epoch 2\n",
      "-------------------------------\n",
      "loss: 0.424148  [  256/60000]\n",
      "loss: 0.453736  [25856/60000]\n",
      "loss: 0.433625  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 83.4%, Avg loss: 0.442346 \n",
      "\n",
      "Epoch 3\n",
      "-------------------------------\n",
      "loss: 0.399523  [  256/60000]\n",
      "loss: 0.398521  [25856/60000]\n",
      "loss: 0.445251  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 83.9%, Avg loss: 0.432194 \n",
      "\n",
      "Epoch 4\n",
      "-------------------------------\n",
      "loss: 0.432532  [  256/60000]\n",
      "loss: 0.393568  [25856/60000]\n",
      "loss: 0.375109  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 81.5%, Avg loss: 0.496383 \n",
      "\n",
      "Epoch 5\n",
      "-------------------------------\n",
      "loss: 0.467584  [  256/60000]\n",
      "loss: 0.326397  [25856/60000]\n",
      "loss: 0.366184  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 82.8%, Avg loss: 0.442437 \n",
      "\n",
      "Epoch 6\n",
      "-------------------------------\n",
      "loss: 0.480928  [  256/60000]\n",
      "loss: 0.300050  [25856/60000]\n",
      "loss: 0.400001  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 85.4%, Avg loss: 0.389477 \n",
      "\n",
      "Epoch 7\n",
      "-------------------------------\n",
      "loss: 0.388301  [  256/60000]\n",
      "loss: 0.377260  [25856/60000]\n",
      "loss: 0.407396  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 85.1%, Avg loss: 0.395412 \n",
      "\n",
      "Epoch 8\n",
      "-------------------------------\n",
      "loss: 0.297468  [  256/60000]\n",
      "loss: 0.371769  [25856/60000]\n",
      "loss: 0.369428  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 85.0%, Avg loss: 0.412034 \n",
      "\n",
      "Epoch 9\n",
      "-------------------------------\n",
      "loss: 0.342873  [  256/60000]\n",
      "loss: 0.418367  [25856/60000]\n",
      "loss: 0.371189  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 85.4%, Avg loss: 0.391052 \n",
      "\n",
      "Epoch 10\n",
      "-------------------------------\n",
      "loss: 0.295546  [  256/60000]\n",
      "loss: 0.456852  [25856/60000]\n",
      "loss: 0.331844  [51456/60000]\n",
      "Test Error: \n",
      " Accuracy: 85.5%, Avg loss: 0.383085 \n",
      "\n",
      "Done!\n"
     ]
    }
   ],
   "source": [
    "new_model = MyLeNet().to(device)\n",
    "new_model.load_state_dict(torch.load('model_weights.pth'))\n",
    "learning_rate = 0.9\n",
    "loss_fn = nn.CrossEntropyLoss()\n",
    "optimizer = torch.optim.SGD(new_model.parameters(), lr=learning_rate)\n",
    "epochs = 10\n",
    "batch_size = 256\n",
    "\n",
    "for t in range(epochs):\n",
    "    print(f\"Epoch {t+1}\\n-------------------------------\")\n",
    "    train_loop(train_dataloader, new_model, loss_fn, optimizer)\n",
    "    test_loop(test_dataloader, new_model, loss_fn)\n",
    "print(\"Done!\")\n",
    "\n",
    "torch.save(new_model.state_dict(), 'model_weights.pth')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc59318a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
